package com.itheima.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.itheima.search.domain.*;
import com.itheima.search.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ElasticsearchClient esClient;

    private final static String INDEX_NAME = "news";

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 搜索新闻
     *
     * @param searchParam
     * @return
     */
    @Override
    public  SearchResult query(SearchParam searchParam) {
        //创List<NewsVO>建es 查询构建对象
        SearchRequest.Builder builder = new SearchRequest.Builder().index(INDEX_NAME);
        //创建复合查询对象
        BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
        if (StrUtil.isNotBlank(searchParam.getKeyword())) {
            //创建查询语句
            boolQueryBuilder.must(m -> m.match(b -> b.field("title").query(searchParam.getKeyword())));
            boolQueryBuilder.filter(query -> query.match(match -> match.field("content").query(searchParam.getKeyword())));
            builder.query(q->q.bool(boolQueryBuilder.build()));
            //直接设置高亮显示
            //builder.highlight(highlight -> highlight.fields("content", highlightField -> highlightField.preTags("<span style='color:red'>")));
            // 设置高亮字段（合并为一个 highlight 调用）
            builder.highlight(hl -> hl
                    .preTags("<em>")
                    .postTags("</em>")
                    .fields("title", f -> f.fragmentSize(100))
                    .fields("content", f -> f.fragmentSize(100))
            );
        }

        //设置分页
        int from = (searchParam.getPageNo()-1) * searchParam.getPageSize();
        int size = searchParam.getPageSize();
        builder.from(from).size(size);
        SearchResponse<NewsVO> esResult;
        try {
            //执行查询
            esResult = esClient.search(builder.build(), NewsVO.class);
        } catch (IOException e) {
            throw new RuntimeException("查询es异常",e);
        }
        //解析返回结果
        //总页数; 通过页大小和总记录数计算出总页数
        //总记录数
        long total = esResult.hits().total().value();
        //总页数; 通过页大小和总记录数计算出总页数
        long pages = total % size == 0 ? total / size : total / size + 1;
        //替换掉查询结果中的高亮字段
        List<Hit<NewsVO>> hits = esResult.hits().hits();
        if (CollUtil.isEmpty(hits)){
            log.info("没有查询到商品{}");
            return SearchResult.empty();
        }
        if (StrUtil.isNotBlank(searchParam.getKeyword())) {
            hits.forEach(hit -> {
                NewsVO newsVO = hit.source();
                if (hit.highlight() != null) {
                    List<String> titleHighlights = hit.highlight().get("title");
                    if (CollUtil.isNotEmpty(titleHighlights)) {
                        newsVO.setTitle(titleHighlights.get(0));
                    }

                    List<String> contentHighlights = hit.highlight().get("content");
                    if (CollUtil.isNotEmpty(contentHighlights)) {
                        newsVO.setContent(contentHighlights.get(0));
                    }
                }
            });
        }
        SearchResult result = new SearchResult(esResult.hits().total().value(),pages,hits.stream().map(hit -> hit.source()).collect(Collectors.toList()));
        return result;
    }

    /**
     * 根据id查询新闻
     *
     * @param newsId
     * @return
     */
    @Override
    public NewsVO queryNewById(Long newsId) {

        //调用redission，使用hash记录news的查询次数
        RMap<String, Long> map = redissonClient.getMap("news:count");
        Long newValue = map.addAndGet(newsId+"", 1L);

        // 1.根据id查询
        GetResponse response;
        try {
            response = esClient.get(g -> g.index(INDEX_NAME).id(newsId.toString()), NewsVO.class);
        } catch (IOException e) {
            throw new RuntimeException("根据id查询es失败",e);
        }
        //封装数据
        if (!response.found()) {
            log.info("根据id查询es为空");
            return null;
        }
        NewsVO newsVO = BeanUtil.toBean(response.source(), NewsVO.class);
        return newsVO;

    }
    /**
     * 新闻添加
     *
     * @param news
     */
    @Override
    public void addNews(String news) {
        NewsVO newsVO = JSONUtil.toBean(news, NewsVO.class);
        try {
            esClient.index(IndexRequest.of(index -> index.index(INDEX_NAME).id(newsVO.getNewsId().toString()).document(newsVO)));
        }catch (Exception e){
            log.error("添加新闻失败：{}",e.getMessage());
            throw new RuntimeException("添加新闻失败", e);
        }
    }

    /**
     * 新闻删除
     *
     * @param newsId
     */
    @Override
    public void delNews(Long newsId) {
        try {
            esClient.delete(d -> d.index(INDEX_NAME).id(newsId.toString()));
        } catch (IOException e) {
            throw new RuntimeException("删除es数据失败",e);
        }

    }

    /**
     * 新闻更新
     *
     * @param news
     */
    @Override
    public void updNews(String news) {
        NewsVO newsVO = JSONUtil.toBean(news, NewsVO.class);
        if (newsVO == null) {
            try {
            esClient.update(u -> u.index(INDEX_NAME).id(newsVO.getNewsId().toString()).doc(newsVO), NewsVO.class);
        } catch (Exception e) {
            throw new RuntimeException("更新es数据失败",e);
        }
        }
    }

    /**}
    }

    /**
     * 初始化新闻
     */
    @Override
    public void initNews(String newsList) {
        List<New> news = JSONUtil.toList(newsList, New.class);
        //2. 将查询到的每页数据添加到BulkRequest对象
        BulkRequest.Builder builder = new BulkRequest.Builder();
        for (New item : news) {
            NewsVO itemDoc = BeanUtil.toBean(item, NewsVO.class);
            ArrayList<String> suggestion = new ArrayList<>();
            suggestion.add(itemDoc.getTitle());
            suggestion.add(itemDoc.getSource());
            //suggestion.add(itemDoc.getNewsType());
            itemDoc.setSuggestion(suggestion);
            builder.operations(op -> op.index(idx -> idx.index("news").id(itemDoc.getNewsId()+"").document(itemDoc)));
        }
        BulkResponse response = null;
        try {
            //3. 执行es的批量请求
            response = esClient.bulk(builder.build());
        } catch (IOException e) {
            throw new RuntimeException("批量保存至es异常",e);
        }

        if (response.errors()) {
            log.error("ES 批量写入出现错误！");
            response.items().forEach(item -> {
                if (item.error() != null) {
                    log.error("错误信息: " + item.error());
                }
            });
        } else {
            log.info("批量写入成功: " + response.items().size() + " 条");
        }
    }

    /**
     * 查询热门新闻
     *
     * @return
     */
    @Override
    public List<NewsVo2> queryHotNews() {
        RMap<String, Long> map = redissonClient.getMap("news:count");
        //1. 获取所有新闻的查询次数,封装成TreeMap，自动排序
        // 1. 创建一个 TreeMap，用于自动按键排序（默认按字符串升序）
        TreeMap<String, Long> treeMap = new TreeMap<>(map);

        //设置treeMap按照值降序排序
        treeMap.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed());
        //判断treeMap大小是否大于20，如果大于则只取前20
        if (treeMap.size() > 20) {
            treeMap = new TreeMap<>(treeMap.entrySet().stream().limit(20).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
        }
        //根据排序后的新闻id，查询新闻
        Set<String> strings = treeMap.keySet();
        //转换strings为List
        List<String> newsIds = strings.stream().collect(Collectors.toList());
        //批量查询
        MgetResponse<NewsVO> newsVOMgetResponse;
        try {
             newsVOMgetResponse = esClient.mget(mget -> mget.index(INDEX_NAME).ids(newsIds), NewsVO.class);
        } catch (IOException e) {
            throw new RuntimeException("批量查询失败",e);
        }
        //解析结果，封装MgetResponse<NewsVO> 为 List<NewsVO>
        List<NewsVO> collect = newsVOMgetResponse.docs().stream().map(item -> item.result().source()).collect(Collectors.toList());
        List<NewsVo2> newsVo2s = new ArrayList<>();
        collect.forEach(item -> {
            NewsVo2 newsVo2 = new NewsVo2();
            newsVo2.setNewsId(item.getNewsId()+"");
            newsVo2.setTitle(item.getTitle());
            newsVo2s.add(newsVo2);
        });
        return newsVo2s;
    }
}
